package com.polito.mad.bikeponyapp.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;


public class SignatureSurfaceView extends View{

	private static final float STROKE_REDUCTION_FACTOR = 1.8f;
	private static final float VELOCITY_FILTER_WEIGHT = 0.4f;
	private static final float MAX_STROKE_WIDTH = 10f;
	private static final float MIN_STROKE_WIDTH = 5f;

	private List<MyPoint> points;
	private Path path;
	private Paint bitmapPaint = new Paint();
	private Bitmap bitmap ;
	private Canvas bitmapCanvas;
	private float lastVelocity=0;
	private float lastStrokeWidth= MAX_STROKE_WIDTH;
	private OnSignatureListener onSignatureListener;
	
	//private Context context;
	
	/*
	 * Note: its necessary to override all the three constructors
	 */
	public SignatureSurfaceView(Context context) {
		this(context,null);
	}
	public SignatureSurfaceView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init();
	}
	private void init()
	{
		bitmapPaint = new Paint();
        bitmapPaint.setDither(true);
        bitmapPaint.setAntiAlias(true);
        bitmapPaint.setColor(Color.BLACK);
      
        bitmapPaint.setStyle(Paint.Style.STROKE);
        bitmapPaint.setStrokeJoin(Paint.Join.ROUND);
        bitmapPaint.setStrokeCap(Paint.Cap.ROUND);
        bitmapPaint.setStrokeWidth(MAX_STROKE_WIDTH);
        /*try a PathEffect to be applied on the paint in order to avoid sharp junctions between points*/
        //bitmapPaint.setPathEffect(new CornerPathEffect(10));
		path = new Path();
		points = new ArrayList<MyPoint>(4);
	}
	
	public void checkBitmap()
	{
		if (bitmap == null) {
		    bitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_8888);
		    bitmapCanvas = new Canvas(bitmap);
		    bitmap.eraseColor(0xffEaEbEb);
		  }
	}
    public boolean onTouchEvent(MotionEvent event) {
    	float eventX = event.getX();
        float eventY = event.getY();
    	
		switch(event.getAction())
    	{
    	case MotionEvent.ACTION_DOWN:
    		if(onSignatureListener!=null && path.isEmpty())
    			onSignatureListener.startSigning();
    		path.moveTo(event.getX(), event.getY());
    		return true;
    	case MotionEvent.ACTION_MOVE:
    	case MotionEvent.ACTION_UP:
    		
    		int historySize = event.getHistorySize();
            for (int i = 0; i < historySize; i++) {
              float historicalX = event.getHistoricalX(i);
              float historicalY = event.getHistoricalY(i);
              points.add(new MyPoint(historicalX, historicalY, event.getHistoricalEventTime(i)));
            }

            points.add(new MyPoint(eventX, eventY, event.getEventTime()));
            
            while(points.size()>=3)
            {
	        	MyPoint point0 = points.get(0);
	    		MyPoint point1 = points.get(1);
	    		MyPoint point2 = points.get(2);
	    		
				checkBitmap();
				float velocity = point2.velocityFrom(point0);
				velocity = VELOCITY_FILTER_WEIGHT * velocity + (1 - VELOCITY_FILTER_WEIGHT) * lastVelocity;
				float strokeWidth = getStrokeWidth(velocity);
				lastStrokeWidth = strokeWidth;
				lastVelocity=velocity;
				path.cubicTo(point0.getX(), point0.getY(), point1.getX(), point1.getY(), point2.getX(), point2.getY());
				bitmapPaint.setStrokeWidth(strokeWidth);
				bitmapCanvas.drawPath(path, bitmapPaint);
				bitmapPaint.setStrokeWidth(MAX_STROKE_WIDTH);
				path.reset();
				path.moveTo(point2.getX(), point2.getY());
				  
				points.remove(2);
				points.remove(1);
				points.remove(0);
            }
            if(event.getAction()==MotionEvent.ACTION_UP)
            {
	            if(points.size()==3)
	            {
	            	MyPoint point0 = points.get(0);
	        		MyPoint point1 = points.get(1);
	        		MyPoint point2 = points.get(2);
					checkBitmap();
					float velocity = point2.velocityFrom(point0);
					velocity = VELOCITY_FILTER_WEIGHT * velocity + (1 - VELOCITY_FILTER_WEIGHT) * lastVelocity;
		          	float strokeWidth = getStrokeWidth(velocity);
		          	lastVelocity=velocity;
		          	lastStrokeWidth = strokeWidth;
					path.cubicTo(point0.getX(), point0.getY(),point1.getX(), point1.getY(), point2.getX(), point2.getY());
					bitmapPaint.setStrokeWidth(strokeWidth);
		            bitmapCanvas.drawPath(path, bitmapPaint);
		            bitmapPaint.setStrokeWidth(MAX_STROKE_WIDTH);
					path.reset();
					path.moveTo(point2.getX(), point2.getY());
					points.clear();
	            }else if(points.size()==2)
	            {
	            	MyPoint point0 = points.get(0);
	        		MyPoint point1 = points.get(1);
	            	checkBitmap();
	            	float velocity = point1.velocityFrom(point0);
	            	velocity = VELOCITY_FILTER_WEIGHT * velocity + (1 - VELOCITY_FILTER_WEIGHT) * lastVelocity;
		          	float strokeWidth = getStrokeWidth(velocity);
		          	lastStrokeWidth = strokeWidth;
		          	lastVelocity=velocity;
	            	path.quadTo(point0.getX(), point0.getY(), point1.getX(), point1.getY());
	            	bitmapPaint.setStrokeWidth(strokeWidth);
		            bitmapCanvas.drawPath(path, bitmapPaint);
		            bitmapPaint.setStrokeWidth(MAX_STROKE_WIDTH);
	             	path.reset();
	             	path.moveTo(point1.getX(), point1.getY());
	            }else if(points.size()==1)
	            {
	            	MyPoint point0 = points.get(0);
	            	checkBitmap();
	            	path.lineTo(point0.getX(), point0.getY());
	            	bitmapCanvas.drawPath(path, bitmapPaint);
	             	path.reset();
	             	path.moveTo(point0.getX(), point0.getY());
	            }
	            points.clear();
            }
            break;
    	default:
            return false;
    	}
    	
    	// Include half the stroke width to avoid clipping.
    	invalidate();
        
    	return true;
	}
    
    private float getStrokeWidth(float velocity) {
		
    	return Math.max(Math.min(lastStrokeWidth + STROKE_REDUCTION_FACTOR *(lastVelocity- velocity),MAX_STROKE_WIDTH),MIN_STROKE_WIDTH);
    	//return Math.min(STROKE_WIDTH + STROKE_REDUCTION_FACTOR *(lastVelocity- velocity),STROKE_WIDTH);
		
	}
	public byte[] getImageByteArray()
    {
    	if(this.getWidth() <=  0 || this.getHeight() <= 0 || bitmap==null )
		{
			return null;
		}
		
    	ByteArrayOutputStream outstream = new ByteArrayOutputStream();
    	/*we need to scale up the butmap maintaining the aspect ratio in order to reduce the size of the resulting image*/
    	
    	int width = bitmap.getWidth();
    	int height = bitmap.getHeight();
    	double ratio = width/height;
    	
    	int newWidth = 680;
    	int newHeight = (int) (newWidth/ratio);
    	
    	Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false).compress(Bitmap.CompressFormat.JPEG, 80, outstream);

		// get byte array here
		byte[] bytearray_of_drawable = outstream.toByteArray();
		
		return bytearray_of_drawable;
    }
	
    public void setImageByteArray(byte[] signatureImage)
    {
    	if(signatureImage != null)
    	{
    		bitmap = BitmapFactory.decodeByteArray(signatureImage,0, signatureImage.length);
    	    invalidate();
    	}
    	//ImageView image=new ImageView(context);
    	//image.setImageBitmap(bmp);
    }
	
    public void clearSignature()
    {
    	path.reset();
 	    bitmap.eraseColor(0xffEaEbEb);
 	    invalidate();
 	    if (onSignatureListener!=null)
 	    	onSignatureListener.stopSigning();
    }
	
    @Override
    public void onDraw(Canvas canvas) {
    	
    	if(bitmap!=null)
    		canvas.drawBitmap(bitmap, 0, 0,bitmapPaint);
    	else 
    		canvas.drawColor(0xffEaEbEb);
	}

	//load any image saved temporarily
	public void loadSurface(byte[] imageArray)
	{
		if(imageArray != null)
		{
			bitmap = BitmapFactory.decodeByteArray(imageArray, 0, imageArray.length);
		}
	}
	
	public File getTempFile(Context context, String url) {
	    File file = null;
	    try {
	        String fileName = Uri.parse(url).getLastPathSegment();
	        file = File.createTempFile(fileName, null, context.getCacheDir());
	    }
	    catch (IOException e) {
	        // Error while creating file
	    }
	    return file;
	}
	
	public class MyPoint
	{
		private float x;
		private float y;
		private long time;
		public MyPoint(float x,float y,long time) {
			this.x=x;
			this.y=y;
			this.time=time;
		}
		public float getX()
		{
			return this.x;
		}
		public float getY()
		{
			return this.y;
		}
		public long getTime()
		{
			return this.time;
		}
		public float velocityFrom(MyPoint start) {
		    return distanceTo(start) / (this.time - start.time);
		  }
		public float distanceTo(MyPoint point)
		{
			float dx = this.x-point.x;
			float dy = this.y-point.y;
			
			return (float) Math.sqrt(dx*dx + dy*dy);
		}
	}
	
	public interface OnSignatureListener
	{
		public void startSigning();
		public void stopSigning();
	}
	public void setOnSignatureListener(OnSignatureListener onSignatureListener)
	{
		this.onSignatureListener=onSignatureListener;
	}
	public OnSignatureListener getOnSignatureListener()
	{
		return this.onSignatureListener;
	}
}